﻿#region GNU GPL Version 3 License

/// Copyright 2013 Shailesh Lolam
/// 
/// This file BaseTask.cs is part of AzEzAuto.
/// 
/// AzEzAuto is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
/// 
/// AzEzAuto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
/// 
/// You should have received a copy of the GNU General Public License along with AzEzAuto. If not, see http://www.gnu.org/licenses/.
/// 
/// History:
/// ______________________________________________________________
/// Created         04-2013             Shailesh Lolam

#endregion
      
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XecMe.Core.Tasks;
using Microsoft.WindowsAzure.Storage;
using System.Configuration;
using Microsoft.WindowsAzure.Storage.Queue;
using Microsoft.WindowsAzure.Storage.Blob;
using AzEzAuto.Management;
using AzEzAuto.Entities;
using AzEzAuto.Orchestrate;
using AzEzAuto.Entities.Requests;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Xml.Serialization;
using AzEzAuto.Utils;

namespace AzEzAuto.Orchestrate.Tasks
{
    public abstract class BaseTask : ITask
    {
        private CloudStorageAccount _storageAccount;
        private CloudQueue _inputQ, _outputQ, _errorQ;
        protected Api _api;

        protected virtual void FollowUp(Request req)
        { 
            // No follow task
            req.CurrentRequestId = null; 
        }

        protected virtual ExecutionState Process(Request req)
        { return ExecutionState.Executed; }

        public virtual ExecutionState OnExecute(ExecutionContext context)
        {
            Request req = ReadInputQueue();

            ///Indicate that there is no work, can take a break
            if (req == null)
                return ExecutionState.Idle;

            ///Request is in progress check the status
            if (!string.IsNullOrEmpty(req.CurrentRequestId))
            {
                Entities.OperationStatus os = _api.GetRequestStatus(req.SubscriptionId, req.CurrentRequestId);
                switch (os.Status)
                {
                    case Entities.RequestStatus.Failed: /// Put the request in the error queue
                        req.Error = new AzEzAuto.Entities.Requests.Error() { Code = os.Error.Code, Message = os.Error.Message, RequestId = req.CurrentRequestId };
                        req.CurrentRequestId = null;
                        WriteToErrorQueue(req);
                        break;
                    case Entities.RequestStatus.InProgress: /// Skip this and it will be invisible for some time
                        return ExecutionState.Executed;
                    case Entities.RequestStatus.Succeeded:/// Put it to next guy in the pipeline
                        FollowUp(req);
                        if(req.CurrentRequestId == null)
                            WriteToOutputQueue(req);
                        break;
                    default:
                        throw new InvalidOperationException(string.Format("Unexpected status {0} returned by the Azure for Operation {1}", os.Status, req.CurrentRequestId));
                }
                return ExecutionState.Executed;
            }

            try
            {
                return Process(req);
            }
            catch (WebException webEx)
            {
                req.CurrentRequestId = webEx.Response.Headers["x-ms-request-id"];
                using (Stream stream = webEx.Response.GetResponseStream())
                using(TraceStream reader = new TraceStream(stream))
                {
                    //string error = reader.ReadToEnd();
                    XmlSerializer ser = new XmlSerializer(typeof(AzEzAuto.Entities.Error));
                    AzEzAuto.Entities.Error err = (AzEzAuto.Entities.Error)ser.Deserialize(reader);
                    req.Error = new Entities.Requests.Error()
                    {
                        Code = err.Code,
                        Message = err.Message,
                        RequestId = req.CurrentRequestId
                    };
                }
                WriteToErrorQueue(req);
                return ExecutionState.Executed;
            }
            catch(Exception e)
            {
                req.Error = new Entities.Requests.Error()
                {
                    Code = "Unhandled exception from Azure Management Portal",
                    Message = e.Message,
                    RequestId = req.CurrentRequestId
                };
                WriteToErrorQueue(req);
                return ExecutionState.Executed;
            }

        }

        public virtual void OnStart(ExecutionContext context)
        {
            _storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageAccount"].ConnectionString);
            CloudQueueClient client = _storageAccount.CreateCloudQueueClient();
            _inputQ = client.GetQueueReference(context.Parameters["input"]);
            _inputQ.CreateIfNotExists();

            _outputQ = client.GetQueueReference(context.Parameters["output"]);
            _outputQ.CreateIfNotExists();

            _errorQ = client.GetQueueReference(context.Parameters["error"]);
            _errorQ.CreateIfNotExists();

            
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection coll = store.Certificates.Find(X509FindType.FindByThumbprint, context.Parameters["cert"], false);
            if (coll.Count == 1)
            {
                _api = new Api(coll[0]);
            }
            else
            {
                throw new ArgumentException("Azure Management Certificate could not be found");
            }
        }

        public virtual void OnStop(ExecutionContext context)
        {
            

        }

        public virtual void OnUnhandledException(Exception e)
        {
            if (e is WebException)
            {
                WebException webEx = (WebException)e;
                Trace.TraceError("Error while executing the task: {0}", webEx);
            }
            Trace.TraceError("Error while executing the task: {0}", e);
        }


        protected string GetSharedAccessSignature(string containerName)
        {
            var container = _storageAccount.CreateCloudBlobClient().GetContainerReference(containerName);
            container.CreateIfNotExists();
            return container.GetSharedAccessSignature(new SharedAccessBlobPolicy()
            {
                Permissions = SharedAccessBlobPermissions.Read,
                SharedAccessExpiryTime = DateTime.UtcNow.AddHours(5)// 5 hours the share access key will be available for Azure to access the packages
            });
        }

        protected Request ReadInputQueue()
        {
            CloudQueueMessage message = _inputQ.GetMessage(TimeSpan.FromSeconds(5));
            if (message != null)
            {
                Request retVal = message.GetValue<Request>();
                retVal.MessageId = message.Id;
                retVal.PopReceipt = message.PopReceipt;
                return retVal;
            }
            return null;
        }

        protected void WriteToOutputQueue(Request request)
        {
            CloudQueueMessage message = new CloudQueueMessage((string)null);
            
            ///Remove the current request id as its been already checked by current task
            request.CurrentRequestId = null;
            
            message.PutValue(request);
            
            _outputQ.AddMessage(message);

            
                //Delete the request from the input queue
            DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
        }

        private void DeleteMessageFromInputQueue(string msgId, string popReceipt, string reqId)
        {
            try
            {
                //Delete the request from the input queue
                _inputQ.DeleteMessage(msgId, popReceipt);
            }
            catch (Exception e)
            {
                Trace.TraceError("Error while deleting the message from input queue for request: {0} with error\n{1}", reqId, e.ToString());
            }

        }

        protected void RewriteToInputQueue(Request request)
        {
            CloudQueueMessage message = new CloudQueueMessage((string)null);

            message.PutValue(request);

            _inputQ.AddMessage(message);

            //Delete the request from the input queue
            DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
        }

        protected void WriteToErrorQueue(Request request)
        {
            CloudQueueMessage message = new CloudQueueMessage((string)null);

            request.CurrentRequestId = null;

            message.PutValue(request);

            _errorQ.AddMessage(message);

            //Delete the request from the input queue
            DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
        }

        protected void CopyProperties(List<AzEzAuto.Entities.Requests.ExtendedProperty> src, List<AzEzAuto.Entities.ExtendedProperty> dest)
        {
            for (int i = 0; i < src.Count; i++)
            {
                AzEzAuto.Entities.Requests.ExtendedProperty s = src[i];
                dest.Add(new Entities.ExtendedProperty() { Name = s.Name, Value = s.Value});
            }
        }

        protected string LoadConfigFromUrl(string url)
        {
            StringBuilder sb = new StringBuilder();
            CloudBlobClient client = _storageAccount.CreateCloudBlobClient();
            ICloudBlob blob = client.GetBlobReferenceFromServer(new Uri(url));
            using (MemoryStream ms = new MemoryStream())
            {
                blob.DownloadToStream(ms);
                return Convert.ToBase64String(ms.ToArray(), Base64FormattingOptions.None);
            }
        }
        
    }
}
